;

export { Gobang }

const DEBUG = false;

const TAG = "Gobang";

/**
 * 棋子的颜色
 */
enum Color {
    /**
     * 无色， 即没有棋子
     */
    Transparent = 0,
    /**
     * 黑色
     */
    BLACK,

    /**
     * 白色
     */
    WHITE,
}



/**
 * 棋子的点
 */
class PiecePoint {
    /**
     * x坐标
     */
    x: number;

    /**
     * y坐标
     */
    y: number;

    /**
     * 颜色
     */
    color: Color;

    constructor(x: number, y: number, color: Color) {
        this.x = x;
        this.y = y;
        this.color = color;
    }
}

/**
 * 裁判接口
 */
interface IChessboardReferee {
    /**
     * 判断是否赢了
     * @param board 棋盘
     * @param point 当前落子信息
     * @returns {boolean} true: 赢了, false: 没有赢
     */
    isWin(board: Array<Array<number>>, point: PiecePoint): boolean;

    /**
     * 当前落子是否有效
     * @param board 棋盘
     * @param point 当前落子信息
     * @returns {boolean} true: 有效, false: 无效
     */
    isValid(board: Array<Array<number>>, point: PiecePoint): boolean;
}

/**
 * 赢了的类型
 */
type WinType = (board : number[][], point: PiecePoint) => boolean;

/**
 * 下一步棋位置生成
 */
type MoveType = (p: PiecePoint) => PiecePoint;

/**
 * 裁判
 */
class ChessboardReferee implements IChessboardReferee {

    private isWinHorizontal: WinType;
    private isWinVertical: WinType;
    private isWinDiagonal: WinType;
    private isWinBackDiagonal: WinType;

    constructor(){
        this.isWinHorizontal = this.createIsWin((p1: PiecePoint) => {
            return {
                x: p1.x + 1,
                y: p1.y,
                color: p1.color
            }
        }, (p2: PiecePoint) => {
            return {
                x: p2.x - 1,
                y: p2.y,
                color: p2.color
            }
        });
        this.isWinVertical = this.createIsWin((p1: PiecePoint) => {
            return {
                x: p1.x,
                y: p1.y + 1,
                color: p1.color
            }
        }, (p2: PiecePoint) => {
            return {
                x: p2.x,
                y: p2.y - 1,
                color: p2.color
            }
        });
        this.isWinDiagonal = this.createIsWin((p1: PiecePoint) => {
            return {
                x: p1.x + 1,
                y: p1.y + 1,
                color: p1.color
            }
        }, (p2: PiecePoint) => {
            return {
                x: p2.x - 1,
                y: p2.y - 1,
                color: p2.color
            }
        });
        this.isWinBackDiagonal = this.createIsWin((p1: PiecePoint) => {
            return {
                x: p1.x + 1,
                y: p1.y - 1,
                color: p1.color
            }
        }, (p2: PiecePoint) => {
            return {
                x: p2.x - 1,
                y: p2.y + 1,
                color: p2.color
            }
        });
    }

    private createIsWin(p1Movement: MoveType, p2Movement: MoveType): WinType {
        return (board, point) => {
            let count = 1;
            let maxCount = 5;
            let p1 = p1Movement(point);
            let p2 = p2Movement(point);
            while (maxCount > 0) {
                let p1IsValid = false;
                let p2IsValid = false;
                if (/* p1有效 */ this.__isValid__(board, p1)) {
                    count++;
                    p1IsValid = true;
                    p1 = p1Movement(p1);
                }
                if (/* p2有效 */ this.__isValid__(board, p2)) {
                    count++;
                    p2IsValid = true;
                    p2 = p2Movement(p2);
                }
                maxCount--;
                if (count >= 5) {
                    return true;
                }
                if (!p1IsValid && !p2IsValid) {
                    return false;
                }
            }
            return false;
        }
    }

    /**
     * 该位置点是否有效
     * @param board 棋盘
     * @param point 点
     * @returns true: 有效点, false: 无效点
     */
    private __isValid__(board: number[][], point: PiecePoint): boolean {
        const rows = board.length;
        const cols = board[0].length;
        const [x, y] = [point.x, point.y];
        return x >= 0 && x < cols && y >= 0 && y < rows && board[x][y] === point.color;
    }

    isWin(board: number[][], point: PiecePoint): boolean {
        return this.isWinHorizontal(board, point) ||
            this.isWinVertical(board, point) ||
            this.isWinDiagonal(board, point) ||
            this.isWinBackDiagonal(board, point);
    }

    isValid(board: number[][], point: PiecePoint): boolean {
        const [x, y] = [point.x, point.y];
        log(x, y, board);
        return board[x][y] == Color.Transparent;
    }
}

/**
 * 棋盘的接口
 */
interface IChessboard {

    /**
     * 将点击位置转换为棋子的点
     * @param boarder 棋盘格子宽度
     * @param point 点击位置
     * @returns {PiecePoint} 转换后的棋子的点
     */
    pointTransform(boarder: number, point: PiecePoint): PiecePoint;

    /**
     * 刷新棋盘, 刷新棋盘信息, 包括棋子的点, 棋子的颜色等
     */
    refreshBoard(): void;
}

/**
 * 五子棋类, 使用时需绑定到canvas(通过id绑定)上, 传入棋子的大小, 边距, 边框等信息
 */
class Gobang implements IChessboard {

    /**
     * 棋盘的列数
     */
    static readonly COLS = 15;

    /**
     * 棋盘的行数
     */
    static readonly ROWS = 15;

    /**
     * 棋盘的二维数组
     */
    private board: Array<Array<number>>;

    /**
     * 棋盘的边距
     */
    private boardMargin: number;

    /**
     * 棋盘的边框
     */
    private boardBorder: number;

    /**
     * 棋子的大小
     */
    private pointSize: number;

    /**
     * 中心点的大小
     */
    private centerPointSize: number;

    /**
     * 棋盘的id
     */
    private readonly id: string;

    /**
     * 棋盘的canvas
     */
    private readonly canvas: HTMLCanvasElement;

    private info: HTMLDivElement | undefined;

    /**
     * 棋盘的context
     */
    private readonly context: CanvasRenderingContext2D | null;

    /**
     * 游戏是否结束
     */
    private gameover: boolean = false;

    /**
     * 当前的棋子颜色, 默认为黑色
     */
    private currentColor: Color = Color.BLACK;

    private readonly chessReferee: IChessboardReferee;

    /**
     * 创建一个棋盘
     * @param id 棋盘的id
     *
     */
    constructor(id: string, boardMargin: number = 20, boardBorder: number = 40, pointSize: number = 10, centerPointSize: number = 5) {
        this.id = id;
        this.canvas = document.querySelector(id) as HTMLCanvasElement;
        this.context = this.canvas ? this.canvas.getContext('2d') as CanvasRenderingContext2D : null;
        this.boardMargin = boardMargin;
        this.boardBorder = boardBorder;
        this.pointSize = pointSize;
        this.centerPointSize = centerPointSize;
        this.board = new Array<number[]>([]);
        this.chessReferee = new ChessboardReferee();
        this.init();
        log("init: this=", this);
    }

    pointTransform(boarder: number, point: PiecePoint): PiecePoint {
        let [x, y] = [point.x, point.y];
        log("pointTransform: point=", point)
        x = Math.max(0, x);
        y = Math.max(0, y);
        let tempX = x / boarder;
        let tempY = y / boarder;
        log("pointTransform: ", "tempX=", tempX, "tempY=", tempY, Math.round(tempX), Math.round(tempY));
        return { x: Math.round(tempX), y: Math.round(tempY), color: point.color };
    }

    private initStyle(): void {
        this.canvas.width = Gobang.COLS * this.boardMargin * 2;
        this.canvas.height = Gobang.ROWS * this.boardMargin * 2;
        this.canvas.style.border = "2px solid red";
        this.canvas.style.margin = "50px";
        this.canvas.style.marginTop = "0px";
        this.info = document.createElement("div");
        this.info.style.textAlign = "center";
        this.info.style.margin = "10px 50px";
        this.info.style.width = this.canvas.width + 'px';
        this.canvas.parentElement?.insertBefore(this.info, this.canvas);
    }

    private init(): void {
        if (!this.canvas || !this.context) {
            error("canvas is null");
            return;
        }
        this.initStyle();
        this.board = Array.from(Array(Gobang.ROWS), () => Array<number>(Gobang.COLS).fill(Color.Transparent));
        this.refreshBoard();

        this.canvas.addEventListener("click", (e: MouseEvent) => {
            if (e.target !== this.canvas) {
                return;
            }
            info(e);
            if (this.gameover) {
                alert("游戏已经结束, 重置棋盘");
                this.reset();
                return;
            }
            const point = this.pointTransform(this.boardBorder, { x: e.offsetX - this.boardMargin, y: e.offsetY - this.boardMargin, color: this.currentColor });
            info('click --> ', point);
            if (this.chessReferee.isValid(this.board, point)) {
                this.currentColor = this.currentColor == Color.BLACK ? Color.WHITE : Color.BLACK;
                this.board[point.x][point.y] = point.color;
                this.drawPoint(point);
                this.refreshBoard();
                if (this.chessReferee.isWin(this.board, point)) {
                    this.gameover = true;
                    const msg = "恭喜" + (point.color === 1 ? "黑棋" : "白棋") + "赢了";
                    this.refershInfo(msg);
                }
            } else {
                alert("该位置已占了, 请重新选择");
            }
        });
    }

    /**
     * 刷新棋盘
     */
    refreshBoard(): void {
        if (!this.canvas || !this.context) {
            error("canvas is null");
            return;
        }
        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.drawBoard();
        for (let x = 0; x < Gobang.COLS; x++) {
            for (let y = 0; y < Gobang.ROWS; y++) {
                this.drawPoint({
                    x: x,
                    y: y,
                    // @ts-ignore
                    color: Color[Color[this.board[x][y]]]
                });
            }
        }
        this.refershInfo();
    }

    /**
     * 绘制棋盘
     */
    private drawBoard(): void {
        if (!this.canvas || !this.context) {
            error("canvas is null");
            return;
        }
        for (let y = 0; y < Gobang.COLS; y++) {
            for (let x = 0; x < Gobang.ROWS; x++) {
                if (y < Gobang.COLS - 1 && x < Gobang.ROWS - 1) {
                    this.context.strokeStyle = "black";
                    this.context.strokeRect(x * this.boardBorder + this.boardMargin, y * this.boardBorder + this.boardMargin, this.boardBorder, this.boardBorder);//绘制格子
                    if (x === y && x === Math.floor(Gobang.COLS / 2)) {
                        // 绘制中心点
                        info(`中心点: ${x},${y}`);
                        this.context.fillStyle = "black";
                        this.context.arc(x * this.boardBorder + this.boardMargin, y * this.boardBorder + this.boardMargin, this.centerPointSize, 0, 2 * Math.PI, false);
                        this.context.fill();
                    }
                }
            }
        }
    }

    /**
     * 绘制棋子
     * @param piece 棋子点信息
     * @returns 无
     */
    private drawPoint(piece: PiecePoint): void {
        if (!this.canvas || !this.context) {
            error("canvas is null");
            return;
        }
        this.context.fillStyle = Color[piece.color];
        this.context.beginPath();
        this.context.arc(piece.x * this.boardBorder + this.boardMargin, piece.y * this.boardBorder + this.boardMargin, this.pointSize, 0, 2 * Math.PI, false);
        this.context.fill();
    }

    private refershInfo(msg?: string): void {
        if (this.info) {
            this.info.innerText = !msg ? '当前为' + (this.currentColor === 1 ? '黑子' : '白子') : msg;
        }
    }

    /**
     * 重置棋盘
     */
    private reset() {
        log("重置棋盘");
        this.gameover = false;
        this.currentColor = Color.BLACK;
        // 重置棋盘
        this.board.forEach((row, y) => {
            row.fill(0);
        });
        this.context?.reset();
        this.refreshBoard();
        this.refershInfo();
    }
}

function log(...message: any[]) {
    if (DEBUG) {
        console.log(`${TAG}: `, ...message);
    }
}

function info(...message: any[]) {
    if (DEBUG) {
        console.info(`${TAG}: `, ...message);
    }
}

function warn(...message: any[]) {
    console.warn(`${TAG}: `, ...message);
}

function error(...message: any[]) {
    console.error(`${TAG}: `, ...message);
}